import mitmproxy.http as mp
import re, threading, os, time, hashlib
import json as json
from enum import Enum

class Log:
    lock = threading.Lock()

    class LogType(Enum):
        NONE = 0
        KEY = 1
        FULL = 2

    class PrintType:
        class FrontColour(Enum):
            DEFAULT = '0'
            BLACK = '30'
            RED = '31'
            GREEN = '32'
            YELLOW = '33'
            BULE = '34'
            PURPLE = '35'
            SKYBULE = '36'
            DARK_GREY = '1;30'
            LIGHT_RED = '1;31'
            LIGHT_GREEN = '1;32'
            LIGHT_YELLOW = '1;33'
            LIGHT_BULE = '1;34'
            LIGHT_PURPLE = '1;35'
            LIGHT_SKYBULE = '1;36'
            WHITE = '1;37'

        class BackGroundColour(Enum):
            BLACK = '40'
            RED = '41'
            GREEN = '42'
            YELLOW = '43'
            BULE = '44'
            PURPLE = '45'
            SKYBULE = '46'
            WHITE = '47'

        class EFFECT(Enum):
            UNDERLINE = '4'
            FLASHING = '5'
            INVERSIVE = '7'
            BLANCKING = '8'

    def get_line(s: str = '-'):
        return s * (os.get_terminal_size().columns - 1)
    '''
    @staticmethod
    def out(*s, seq='', **args) -> None:
        pass
    '''
    @staticmethod
    def well(*string: str, seq: str = '', thread: str = '主线程', enable_info: bool = True):
        if enable_info:
            Log.lock.acquire()
            try:
                print('\033[1;30m[{}][√]<{}>\033[0m'.format(time.strftime('%H:%M:%S'), thread),
                      seq.join('\033[1;32m{}\033[0m'.format(s) for s in string))
            finally:
                Log.lock.release()
        else:
            print(seq.join('\033[1;32m{}\033[0m'.format(s) for s in string))

    @staticmethod
    def normal(*string: str, seq: str = '', thread: str = '主线程', enable_info: bool = True):
        if enable_info:
            Log.lock.acquire()
            try:
                print('\033[1;30m[{}][-]<{}>\033[0m'.format(time.strftime('%H:%M:%S'), thread),
                      seq.join('\033[1;36m{}\033[0m'.format(s) for s in string))
            finally:
                Log.lock.release()
        else:
            print(seq.join('\033[1;36m{}\033[0m'.format(s) for s in string))

    @staticmethod
    def wanning(*string: str, seq: str = '', thread: str = '主线程', enable_info: bool = True):
        if enable_info:
            Log.lock.acquire()
            try:
                print('\033[1;30m[{}][!]<{}>\033[0m'.format(time.strftime('%H:%M:%S'), thread),
                      seq.join('\033[1;33m{}\033[0m'.format(s) for s in string))
            finally:
                Log.lock.release()
        else:
            print(seq.join('\033[1;33m{}\033[0m'.format(s) for s in string))

    @staticmethod
    def error(*string: str, seq: str = '', thread: str = '主线程', enable_info: bool = True):
        if enable_info:
            Log.lock.acquire()
            try:
                print('\033[1;30m[{}][X]<{}>\033[0m'.format(time.strftime('%H:%M:%S'), thread),
                      seq.join('\033[1;31m{}\033[0m'.format(s) for s in string))
            finally:
                Log.lock.release()
        else:
            print(seq.join('\033[1;31m{}\033[0m'.format(s) for s in string))

    def sys_info(*string: str, seq: str = ''):
        print(seq.join('\033[1;30m{}\033[0m'.format(s) for s in string))

BiliAPPkey = '8783abfb533544c59e598cddc933d1bf'

class urltool :

    def match(url, *matches) -> bool:
        return False in (re.match(match, url) == None for match in matches) or True in (match in url for match in matches)

PATH = os.path.join(os.path.split(os.path.abspath(__file__))[0], 'account.json')
if not os.path.exists(PATH):
    with open(PATH, 'w', encoding='utf-8') as f:
        f.write('{}')
    account_data = {}
else:
    with open(PATH, 'r', encoding='utf-8') as f:
        account_data = json.load(f)

def save(key: str, value: str) -> None:
    account_data[key] = value
    with open(PATH, 'w', encoding='utf-8') as f:
        json.dump(account_data, f, ensure_ascii=False, indent=4)

def read(key: str) -> str:
    return account_data[key] if key in account_data.keys() else None

def generate_bilisdk_sign(body: dict) -> str:
    if 'sign' in body:
        body.pop('sign')
    keys = list(body.keys())
    keys.sort()
    return hashlib.md5((''.join(str(body[key]) for key in keys) + BiliAPPkey).encode()).hexdigest()

class Main:

    def get_header(data: str, flow : mp.HTTPFlow) -> dict:
        return {
            'Cache-Control': 'no-cache',
            'Content-Length': str(len(data)),
            'Content-Type': 'application/json; charset=utf-8',
            'Date': 'Sat, 07 MAR 2022 {} UTC'.format(time.strftime('%H:%M:%S')),
            'seqnum': str(flow.request.headers['seqnum'])
        }

    def request(self, flow : mp.HTTPFlow):
        url = flow.request.url
        if 'biligame' in url:
            body = dict(flow.request.urlencoded_form)
            if 'sdk_ver' in body:
                body.pop('sdk_ver')
            body['sign'] = generate_bilisdk_sign(body)
            flow.request.urlencoded_form = body
        if 'beat' in url or 'track' in url and 'ak-gs' not in url:
            flow.request.host ='none.com'
            flow.request.url ='https://none.com/block'
            flow.response = mp.Response.make(
                status_code = 400,
                content = 'block'.encode('utf-8'))

    def response(self, flow : mp.HTTPFlow):
        url = flow.request.url
        '''if 'user.token.oauth.login' in url:
            res = json.loads(flow.response.content)
            res['access_key'] = 'xxx';
            flow.response.set_text(json.dumps(res, ensure_ascii=False))
            Log.normal('access_key')'''
        if urltool.match(url, 'https://as.hypergryph.com/u8/user/v1/getToken'):
            if flow.response.status_code != 200:
                channelUid = json.loads(json.loads(flow.request.content)['extension'])['uid']
                Log.well('检测到channelUid: ' + channelUid)
                uid = read(channelUid)
                if uid != None:
                    flow.response.set_text(json.dumps({
                        "channelUid": channelUid,
                        "error": "",
                        "extension": "{\"nickName\":\"doctor\"}",
                        "isGuest": 0,
                        "result": 0,
                        "token": "1",
                        "uid": uid
                    }))
                    flow.response.status_code = 200
                else:
                    Log.error('未存储的账号数据，请法定时间登录并记录数据')
            else:
                res = json.loads(flow.response.content)
                save(res['channelUid'], res['uid'])
            Log.normal('getToken')
            return
        if urltool.match(url, 'https://ak-gs-.*?.hypergryph.com/account/login'):
            res = json.loads(flow.response.content)
            if res['result'] != 0:
                uid = json.loads(flow.request.content)['uid']
                Log.well('检测到uid: ' + uid)
                secret = read(uid)
                if secret != None:
                    flow.response.set_text(json.dumps({
                        "result": 0,
                        "secret": secret,
                        "serviceLicenseVersion": 0,
                        "uid": uid
                    }))
                else:
                    Log.error('未存储的账号数据，请法定时间登录并记录数据')
                flow.response.status_code = 200
            else:
                save(res['uid'], res['secret'])
            Log.normal('account/login')
            return

addons = [Main()]
if __name__ =='__main__':
    from mitmproxy import options
    from mitmproxy.tools.dump import DumpMaster
    Log.normal ('请输入数字设置抓包端口（尽量大于1000，必须小于等于65535）：')
    port = int(input())
    opts = options.Options(listen_host='127.0.0.1', listen_port=port)
    m = DumpMaster(options=opts)
    m.addons.add(*addons)
    m.run()